package com.blacksee.bluetoothpoker.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import com.blacksee.bluetoothpoker.ui.PokerCanvas;

//TODO add kill server method
public class BluePokerServerImpl implements IMessageSender{

	// TODO - try to find a way to generate the uuid automatically
	public final static UUID NETWORK_UUID = new UUID("F0E0D0C0B0A000908070605040302010",false);

	public final static String NETWORK_PROTOCOL = "btspp://";

	public final static String SERVER_ADDRESS   = "localhost:";



	// properties :

	private String serviceName = "Blue Poker";

	private LocalDevice localDevice = null;

	private StreamConnectionNotifier notifier = null;

	private ServiceRecord record = null;

	private Thread clientProcessorThread = null;

	// TODO change with Hashtable
	private Vector connections = null;

	private Vector clientStreams = null; 
	private boolean inquiryReady = false;

	/**
	 * 
	 */
	public BluePokerServerImpl(){
		try{
			// extract the local device
			localDevice = LocalDevice.getLocalDevice();

//			PokerCanvas.instance.postDebugLn("A:" + localDevice.getBluetoothAddress());//TODO remove
			
			connections = new Vector();

			clientStreams = new Vector();
		} catch (BluetoothStateException e) {
			e.printStackTrace();// TODO change the logging
		} finally{

		}

		/*
		 			localDevice = LocalDevice.getLocalDevice();

			localDevice.setDiscoverable(DiscoveryAgent.GIAC);

			String url = "btspp://localhost:F0E0D0C0B0A000908070605040302010;name=Test;authorize=false";

			// create notifier
			notifier = (StreamConnectionNotifier)Connector.open(url);

			// create record for later updates
			record = localDevice.getRecord(notifier);

			System.out.println("After creating record and notifier!");

			// open connection
			StreamConnection connection = notifier.acceptAndOpen(); // accepts the connection

			System.out.println("Connection open!");

			inputStream = connection.openDataInputStream();

			while (inputStream.available() == 0){
				System.out.println("Available " + inputStream.available());
			}

			String message = inputStream.readUTF();
		 */
	}

	/**
	 * 
	 */
	public void startServer(){
		try {
			System.out.println("LOCAL DEVICE: " + localDevice);//TODO remove

			localDevice.setDiscoverable(DiscoveryAgent.GIAC);

			String max = LocalDevice.getProperty("bluetooth.connected.devices.max");

			System.out.println("MAXIMUM: " + max); //TODO remove

			String url = createConnectionURL();

			System.out.println("Connection URL for server: " + url);//TODO remove

			prepareConnection(url);

			startClientProcessor();
		} catch (BluetoothStateException e) {
			e.printStackTrace(); //TODO modify the way the logging is done
		} catch (IOException e) {
			e.printStackTrace(); //TODO modify the way the logging is done
		}
	}

	private void startClientProcessor() {
		clientProcessorThread = new Thread(new AccepterThread(this));

		System.out.println("Client Processor Thread: " + clientProcessorThread); //TODO remove

		clientProcessorThread.start();

	}

	/**
	 * 
	 * @param url
	 * @throws IOException
	 */
	private void prepareConnection(String url) throws IOException {
		System.out.println("PREPARE CONNECTION"); //TODO remove
		notifier  = (StreamConnectionNotifier)Connector.open(url);

		System.out.println("NOTIFIER : " + notifier); //TODO remove

		record    = localDevice.getRecord(notifier);

		System.out.println("RECORD : " + record); //TODO remove
	}

	/**
	 * 
	 * @return
	 */
	private String createConnectionURL() {
		StringBuffer buffer = new StringBuffer();

		buffer.append(NETWORK_PROTOCOL);
		buffer.append(SERVER_ADDRESS);
		buffer.append(NETWORK_UUID);
		buffer.append(";name=" + serviceName);
		buffer.append(";authorize=false");
		buffer.append(";master=true");
		buffer.append(";encrypt=false");

		return buffer.toString();
	}

	private void addConnection(StreamConnection connection){
		synchronized (connections) {
			if (connections.indexOf(connection) == -1){

				System.out.println("Added connection: " + connection); //TODO remove

				connections.addElement(connection);		

//				System.out.println("BCAST");

//				broadcastMessage("");
			}
		}
	}

	private void addStream(DataOutputStream dataOutputStream) {
		synchronized (clientStreams) {
			clientStreams.addElement(dataOutputStream);
		}
	}

	protected synchronized void receiveMessage(int clientId, String message){
		NetCommunicator.getInstance().receiveMssage(message,clientId);
	}

	public void sendMessage(int clientId,String message){
		try {
			DataOutputStream output = (DataOutputStream)clientStreams.elementAt(clientId);
			
			// add 1 for normal message
			output.writeUTF("1" + message);
			output.flush();
		} catch(IOException e){
			e.printStackTrace();
		}
	}

	// TODO add processing elements for each client
	private static class AccepterThread implements Runnable{

		private Thread processorThread = null;

		private BluePokerServerImpl parent = null;

		private StreamConnectionNotifier notifier;

		public AccepterThread(){
			processorThread = new Thread(this);

			processorThread.start();
		}

		public AccepterThread(BluePokerServerImpl poker){
			this();

			this.parent = poker;
		}

		public void run(){
			//TODO modify condition - exit condition if something is wrong
			while(true){
				StreamConnection connection = null;
				try {
					//TODO check for a better solution
					connection = (StreamConnection)parent.notifier.acceptAndOpen();

				} catch (IOException e) {
					continue;
				}

				if (connection != null){
					synchronized (parent.connections) {
						parent.addConnection(connection);
						try {
							parent.addStream(connection.openDataOutputStream());
							
							int clientID = parent.clientStreams.size()-1;
							ClientProcessor processor = new ClientProcessor(clientID,parent,connection);
							Thread clientProcessor = new Thread(processor);
							clientProcessor.start();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}

	private static class ClientProcessor implements Runnable{

		private int clientId;

		private BluePokerServerImpl parent = null;

		private StreamConnection    connection = null;

		private DataInputStream clientDataInputStream = null;

		public ClientProcessor(){

		}

		public ClientProcessor(int clientId, BluePokerServerImpl parent, StreamConnection connection){
			this.clientId   = clientId;
			this.parent     = parent;
			this.connection = connection;

			prepareCommunication();
		}

		private void prepareCommunication() {
			try {
				clientDataInputStream  = connection.openDataInputStream();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		public void run(){
			while (true){
				System.out.println("MESSSAGE WAITING" ); //TODO remove
				try {
					while (clientDataInputStream.available() == 0){
						// wait
					}
					
					System.out.println("DATA AVAILABLE: " + clientDataInputStream.available()); //TODO remove

					String message = clientDataInputStream.readUTF();

					System.out.println("MESSAGE RECEIVED: " + message); //TODO remove
					
					synchronized (parent) {
						
						// remove the first char of the message to be received
						String mssg = "";
						if (message != null && message.length() > 0) {
							mssg = message.substring(1);
							System.out.println("RECEIVED MESSAGE: " + message); //TODO remove
							
							parent.receiveMessage(clientId, mssg);
							
							// if it is a broadcast we should further broadcast
							if (message.startsWith("0")) {
								parent.broadcastMessage(message, clientId);
							}
						}
					}

				} catch (IOException e) {
					e.printStackTrace();
				} catch(Exception e){
					System.out.println("THROWABLE!");//TODO remove
				}
			}
		}
	}

	public void broadcastMessage(String message){
		// add 0 to the broadcast message
		System.out.println("Broadcasting mssg to all: " + message);
		for (int i = 0; i < clientStreams.size(); i++) {
			try {
				DataOutputStream output = (DataOutputStream)clientStreams.elementAt(i);
				
				// add 0 for broadcast message
				output.writeUTF("0" + message);
				output.flush();
			} catch(IOException e){
				e.printStackTrace();
			}
		}
	}
	
	private void broadcastMessage(String message, int excludeId) {
		// internal broadcast dose not need to add 0
		System.out.println("Broadcasting mssg to all: " + message);
		for (int i = 0; i < clientStreams.size(); i++) {
			if (i == excludeId){
				continue;
			}
			try {
				DataOutputStream output = (DataOutputStream)clientStreams.elementAt(i);
				
				output.writeUTF(message);
				output.flush();
			} catch(IOException e){
				e.printStackTrace();
			}
		}
	}

	public String getServiceName() {
		return serviceName;
	}

	public void setServiceName(String serviceName) {
		this.serviceName = serviceName;
	}

	public void setInquiryReady(boolean inquiryReady) {
		this.inquiryReady = inquiryReady;
	}
}
